/**
 * 创建一个scroller的dom
 */
import {throttle} from 'lodash'
import erd from "element-resize-detector";
import elementResizeDetectorMaker from "element-resize-detector";

class Scroller {
    private readonly targetTableWrapperEl: any;
    private fullwidth: boolean;
    private readonly mode: string;
    dom: HTMLDivElement;
    private bar: HTMLDivElement;
    private thumb: HTMLDivElement;
    private readonly checkIsScrollBottom: any;
    private readonly syncDestroyHandler: () => void;
    private detector: elementResizeDetectorMaker.Erd;

    /**
     * 给tableBody创建一个scroller
     * @param {Element} targetTableWrapperEl
     * @param {string} mode 模式，可选值为'hover'或者'always'
     * @param bottom 距离底部的距离
     * @param zIndex z-index值
     */
    constructor(targetTableWrapperEl, {mode = 'hover', bottom = 0, zIndex = 3}) {
        if (!targetTableWrapperEl) {
            throw new Error('need have table element')
        }
        this.targetTableWrapperEl = targetTableWrapperEl
        this.fullwidth = false
        this.mode = mode

        /**
         * 创建相关dom
         */
        const scroller = document.createElement('div')
        scroller.classList.add('el-scrollbar')
        scroller.style.height = '12px'
        scroller.style.position = 'fixed'
        scroller.style.bottom = bottom + 'px'
        scroller.style.zIndex = String(zIndex)

        this.dom = scroller
        this.resetScroller()

        const bar = document.createElement('div')
        bar.classList.add('el-scrollbar__bar', 'is-horizontal')
        this.bar = bar
        scroller.appendChild(bar)

        const thumb = document.createElement('div')
        thumb.classList.add('el-scrollbar__thumb')
        bar.appendChild(thumb)
        this.thumb = thumb

        /**
         * 初始化配置
         */
        const instance = this
        this.checkIsScrollBottom = throttle(function () {
                const {scrollWidth, width} = targetTableWrapperEl.getBoundingClientRect()
                if (width <= scrollWidth) {
                    instance.hideScroller()
                } else {
                    // 需要重新设置一次当前宽度
                    instance.resetBar(false)

                    // 显示当前的bar
                    instance.showScroller()
                }
            }
            , 1000 / 60)
        document.addEventListener('scroll', this.checkIsScrollBottom) // 全局判断是否需要显示scroller

        // 自动同步,table => scroller
        targetTableWrapperEl.addEventListener('scroll', throttle(function () {
            instance.resetThumbPosition()
        }, 1000 / 60))

        // 自动同步 scroller => table
        this.syncDestroyHandler = this.initScrollSyncHandler()

        // 监听table的dom变化，自动重新设置
        this.detector = erd();
        this.detector.listenTo(targetTableWrapperEl, () => {
            setTimeout(() => {
                instance.resetBar()
                instance.resetScroller()
                instance.resetThumbPosition()
                instance.checkIsScrollBottom()
            })
        })
        setTimeout(() => {
            instance.resetBar()
            instance.resetScroller()
            instance.resetThumbPosition()
            instance.checkIsScrollBottom()
        }, 200)
    }

    /**
     * 自动设置Bar
     * @param {boolean} changeScrollerVisible 是否开启自动设置滚动条显示与否
     */
    resetBar(changeScrollerVisible = true) {
        const {targetTableWrapperEl} = this
        const widthPercentage = (targetTableWrapperEl.clientWidth * 100 / targetTableWrapperEl.scrollWidth)
        const thumbWidth = Math.min(widthPercentage, 100)
        this.thumb.style.width = `${thumbWidth}%`

        this.fullwidth = thumbWidth >= 100

        if (changeScrollerVisible) {
            if (this.fullwidth) {
                this.hideScroller()
            } else {
                this.checkIsScrollBottom()
            }
        }
    }

    resetThumbPosition() {
        this.thumb.style.transform = `translateX(${this.moveX}%)`
    }

    resetScroller() {
        const {targetTableWrapperEl, dom} = this
        const boundingClientRect = targetTableWrapperEl.getBoundingClientRect()
        dom.style.left = boundingClientRect.left + 'px'
        dom.style.width = boundingClientRect.width + 'px'
    }

    get moveX() {
        const {targetTableWrapperEl} = this
        return ((targetTableWrapperEl.scrollLeft * 100) / targetTableWrapperEl.clientWidth)
    }

    /**
     * 让scroller的拖动行为和table的同步
     * 处理类似element-ui的拖拽处理
     */
    initScrollSyncHandler() {
        let cursorDown = false
        let tempClientX = 0
        let rate = 1

        const {thumb, targetTableWrapperEl, bar} = this

        function getRate() {
            // 计算一下变换比例，拖拽走的是具体数字，但是这个实际上应该是按照比例变的
            return bar.offsetWidth / thumb.offsetWidth
        }

        const mouseMoveDocumentHandler = throttle(
            /** @param {MouseEvent} e */
            function (e) {
                if (!cursorDown) {
                    return
                }
                const {clientX} = e
                const offset = clientX - tempClientX
                const originTempClientX = tempClientX
                tempClientX = clientX

                const tempScrollLeft = targetTableWrapperEl.scrollLeft
                targetTableWrapperEl.scrollLeft += offset * rate
                if (tempScrollLeft === targetTableWrapperEl.scrollLeft) {
                    tempClientX = originTempClientX
                }
            }, 1000 / 60)

        function mouseUpDocumentHandler() {
            cursorDown = false
            document.removeEventListener('mousemove', mouseMoveDocumentHandler)
            document.removeEventListener('mouseup', mouseUpDocumentHandler)
            document.onselectstart = null
        }

        /**
         * 拖拽处理
         * @param {MouseEvent} e
         */
        function startDrag(e) {
            e.stopImmediatePropagation()
            cursorDown = true
            document.addEventListener('mousemove', mouseMoveDocumentHandler)
            document.addEventListener('mouseup', mouseUpDocumentHandler)
            document.onselectstart = () => false
        }

        thumb.onmousedown = function (e) {
            // prevent click event of right button
            if (e.ctrlKey || e.button === 2) {
                return
            }

            const {clientX} = e
            tempClientX = clientX
            rate = getRate()
            startDrag(e)
        }

        /**
         * 点击槽快速移动
         * @param {PointerEvent} e
         */
        bar.onclick = function (e) {
            const {target} = e
            if (target !== bar) {
                return
            }
            rate = getRate()
            const {clientX} = e
            let offset: number
            const thumbPosition = thumb.getBoundingClientRect()
            if (thumbPosition.left >= clientX) {
                offset = (clientX - thumbPosition.left)
            } else {
                offset = clientX - thumbPosition.left - thumbPosition.width
            }

            const targetScrollLeft = targetTableWrapperEl.scrollLeft + offset * rate
            targetTableWrapperEl.scrollTo({
                left: targetScrollLeft,
                behavior: 'smooth'
            })
        }

        return function () {
            document.removeEventListener('mouseup', mouseUpDocumentHandler)
        }
    }

    /**
     * 显示整体
     */
    showScroller() {
        if (!this.fullwidth) {
            this.dom.style.display = 'initial'
        }
    }

    /**
     * 隐藏整体
     */
    hideScroller() {
        if (this.mode === 'force') {
            this.dom.style.display = 'initial'
        } else {
            this.dom.style.display = 'none'
        }
    }

    /**
     * 显示滚动条
     */
    showBar() {
        this.bar.style.opacity = String(0.7)
    }

    /**
     * 隐藏滚动条
     */
    hideBar() {
        if (this.mode === 'force') {
            this.bar.style.opacity = String(0.7)
        } else {
            this.bar.style.opacity = String(0)
        }
    }

    destroy() {
        this.detector.uninstall(this.targetTableWrapperEl);
        document.removeEventListener('scroll', this.checkIsScrollBottom)
        this.syncDestroyHandler()
    }
}

/** @type {Vue.DirectiveOptions} */
export const horizontalScroll = {
    inserted(el, binding) {
        const {value} = binding
        const {mode = 'hover'} = value
        const tableBodyWrapper = el.querySelector('.el-table__body-wrapper')
        const scroller = new Scroller(tableBodyWrapper, value)

        el.appendChild(scroller.dom)
        el.horizontalScroll = scroller

        if (mode === 'hover') {
            el.addEventListener('mouseover', scroller.showBar.bind(scroller))
            el.addEventListener('mouseleave', scroller.hideBar.bind(scroller))
        } else {
            scroller.showBar()
        }
    },
    unbind(el) {
        el.horizontalScroll.destroy()
    }
}

/**
 * 插件
 */
export const Plugin = {
    install(Vue) {
        Vue.directive('horizontalScroll', horizontalScroll)
    }
}

export default Plugin
